package com.link.base.core.basic.interceptors.mybatis.system;

import com.link.core.util.SpringUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;

import java.sql.SQLException;
import java.util.Properties;

/**
 * @Author: qinxuegang
 * @Description: mybatis拦截器，处理相关sql
 * @Date: 2018/11/1 10:30
 */
@Intercepts(value = {
        @Signature(type = Executor.class,
                method = "update",
                args = {MappedStatement.class, Object.class})})
public class MybatisSqlInterceptor implements Interceptor {
    private final String SQL_OPERATE_TYPE_SELECT = "select";
    private final String SQL_OPERATE_TYPE_INSERT = "insert";
    private final String SQL_OPERATE_TYPE_UPDATE = "update";
    private final String SQL_OPERATE_TYPE_DELETE = "delete";
    /**
     * 默认ObjectFactory
     */
    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    /**
     * 默认ObjectWrapperFactory
     */
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    /**
     * 默认ReflectorFactory
     */
    private static final ReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取sql
        BoundSql boundSql = getSqlByInvocation(invocation);
        if (boundSql != null && StringUtils.isBlank(boundSql.getSql())) {
            return invocation.proceed();
        }

        // 获取业务系统中配置的sql处理器（service）的名称
        String[] interceptorNames = getInterceptorNames(invocation);
        if (interceptorNames == null || interceptorNames.length == 0) {
            return invocation.proceed();
        }

        // sql交由处理类处理
        String sql2Reset = processSqlByInterceptor(invocation, boundSql, interceptorNames);

        // 包装sql后，重置到invocation中
        resetSql2Invocation(invocation, sql2Reset);

        // 返回，继续执行
        return invocation.proceed();

    }

    @Override
    public Object plugin(Object obj) {
        return Plugin.wrap(obj, this);
    }

    @Override
    public void setProperties(Properties properties) {
        //doSomeThing
    }

    /**
     * @Author: qinxuegang
     * @Description:获取sql
     * @Date:10:40 2018/11/1
     * @Params:
     */
    private BoundSql getSqlByInvocation(Invocation invocation) {
        final Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameterObject = args[1];
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        return boundSql;
    }

    /**
     * @Author: qinxuegang
     * @Description: 通过获取的springMVC中的处理类的bean名称，得到实例和处理方法，循环处理sql
     * @Date:10:40 2018/11/1
     * @Params:
     */
    private String processSqlByInterceptor(Invocation invocation, BoundSql boundSql, String[] interceptorNames) {
        if (interceptorNames == null || interceptorNames.length == 0) {
            return boundSql.getSql();
        }

        String resultSql = boundSql.getSql();
        for (int i = 0; i < interceptorNames.length; i++) {
            Object interceptorObj = SpringUtil.getBean(interceptorNames[i]);
            if (interceptorObj == null) {
                continue;
            }
            resultSql = ((SqlInterceptor) interceptorObj).doInterceptor(invocation, boundSql);
        }

        return resultSql;
    }

    /**
     * @Author: qinxuegang
     * @Description:包装sql后重置sql
     * @Date:10:41 2018/11/1
     * @Params:
     */
    private void resetSql2Invocation(Invocation invocation, String sql) throws SQLException {
        final Object[] args = invocation.getArgs();
        MappedStatement statement = (MappedStatement) args[0];
        Object parameterObject = args[1];
        BoundSql boundSql = statement.getBoundSql(parameterObject);
        MappedStatement newStatement = newMappedStatement(statement, new BoundSqlSqlSource(boundSql));
        MetaObject msObject = MetaObject.forObject(newStatement, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY);
        msObject.setValue("sqlSource.boundSql.sql", sql);
        args[0] = newStatement;
    }

    private MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.Builder builder =
                new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
            StringBuilder keyProperties = new StringBuilder();
            for (String keyProperty : ms.getKeyProperties()) {
                keyProperties.append(keyProperty).append(",");
            }
            keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
            builder.keyProperty(keyProperties.toString());
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());

        return builder.build();
    }

    /**
     * @Author: qinxuegang
     * @Description: 定义一个内部辅助类，作用是包装sql
     * @Date:10:42 2018/11/1
     * @Params:
     */
    class BoundSqlSqlSource implements SqlSource {


        private BoundSql boundSql;


        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }


        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }


    }

    /**
     * @Author: qinxuegang
     * @Description: 获取操作类型
     * @Date:10:43 2018/11/1
     * @Params:
     */
    private String getOperateType(Invocation invocation) {
        final Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        SqlCommandType commondType = ms.getSqlCommandType();
        if (commondType.compareTo(SqlCommandType.SELECT) == 0) {
            return SQL_OPERATE_TYPE_SELECT;
        }
        if (commondType.compareTo(SqlCommandType.INSERT) == 0) {
            return SQL_OPERATE_TYPE_INSERT;
        }
        if (commondType.compareTo(SqlCommandType.UPDATE) == 0) {
            return SQL_OPERATE_TYPE_UPDATE;
        }
        if (commondType.compareTo(SqlCommandType.DELETE) == 0) {
            return SQL_OPERATE_TYPE_DELETE;
        }
        return null;
    }

    /**
     * @Author: qinxuegang
     * @Description:获取properties中的常量配置
     * @Date:10:43 2018/11/1
     * @Params:
     */
    private String[] getInterceptorNames(Invocation invocation) {
        String operateType = getOperateType(invocation);
        if (StringUtils.isBlank(operateType)) {
            return new String[]{};
        }
        String interceptorNameString = "";
        if (SQL_OPERATE_TYPE_SELECT.equals(operateType)) {
            interceptorNameString = "selectInterceptor";
        } else if (SQL_OPERATE_TYPE_INSERT.equals(operateType)) {
            interceptorNameString = "insertInterceptor";
        } else if (SQL_OPERATE_TYPE_UPDATE.equals(operateType)) {
            interceptorNameString = "updateInterceptor";
        } else if (SQL_OPERATE_TYPE_DELETE.equals(operateType)) {
            interceptorNameString = "deleteInterceptor";
        } else {
            System.out.println("未找到类型");
        }
        if (StringUtils.isBlank(interceptorNameString)) {
            return new String[]{};
        }

        return interceptorNameString.split(",");
    }


}
